phpDocNodeVisitors[] = $phpDocNodeVisitor; } public function traverse(Node $node) : void { foreach ($this->phpDocNodeVisitors as $phpDocNodeVisitor) { $phpDocNodeVisitor->beforeTraverse($node); } $node = $this->traverseNode($node); foreach ($this->phpDocNodeVisitors as $phpDocNodeVisitor) { $phpDocNodeVisitor->afterTraverse($node); } } /** * @param callable(Node $node): (int|null|Node) $callable */ public function traverseWithCallable(Node $node, string $docContent, callable $callable) : void { $callablePhpDocNodeVisitor = new CallablePhpDocNodeVisitor($callable, $docContent); $this->addPhpDocNodeVisitor($callablePhpDocNodeVisitor); $this->traverse($node); } /** * @template TNode of Node * @param TNode $node * @return TNode */ private function traverseNode(Node $node) : Node { $objectPublicPropertiesToValues = \get_object_vars($node); $subNodeNames = \array_keys($objectPublicPropertiesToValues); foreach ($subNodeNames as $subNodeName) { $subNode =& $node->{$subNodeName}; if (\is_array($subNode)) { $subNode = $this->traverseArray($subNode); } elseif ($subNode instanceof Node) { $breakVisitorIndex = null; $traverseChildren = \true; foreach ($this->phpDocNodeVisitors as $visitorIndex => $phpDocNodeVisitor) { $return = $phpDocNodeVisitor->enterNode($subNode); if ($return !== null) { if ($return instanceof Node) { $subNode = $return; } elseif ($return === self::DONT_TRAVERSE_CHILDREN) { $traverseChildren = \false; } elseif ($return === self::DONT_TRAVERSE_CURRENT_AND_CHILDREN) { $traverseChildren = \false; $breakVisitorIndex = $visitorIndex; break; } elseif ($return === self::STOP_TRAVERSAL) { $this->stopTraversal = \true; } elseif ($return === self::NODE_REMOVE) { $subNode = null; continue 2; } else { throw new InvalidTraverseException('enterNode() returned invalid value of type ' . \gettype($return)); } } } if ($traverseChildren) { $subNode = $this->traverseNode($subNode); if ($this->stopTraversal) { break; } } foreach ($this->phpDocNodeVisitors as $visitorIndex => $phpDocNodeVisitor) { $phpDocNodeVisitor->leaveNode($subNode); if ($breakVisitorIndex === $visitorIndex) { break; } } } } return $node; } /** * @param array $nodes * @return array */ private function traverseArray(array $nodes) : array { foreach ($nodes as $key => &$node) { // can be string or something else if (!$node instanceof Node) { continue; } $traverseChildren = \true; $breakVisitorIndex = null; foreach ($this->phpDocNodeVisitors as $visitorIndex => $phpDocNodeVisitor) { $return = $phpDocNodeVisitor->enterNode($node); if ($return !== null) { if ($return instanceof Node) { $node = $return; } elseif ($return === self::DONT_TRAVERSE_CHILDREN) { $traverseChildren = \false; } elseif ($return === self::DONT_TRAVERSE_CURRENT_AND_CHILDREN) { $traverseChildren = \false; $breakVisitorIndex = $visitorIndex; break; } elseif ($return === self::STOP_TRAVERSAL) { $this->stopTraversal = \true; } elseif ($return === self::NODE_REMOVE) { // remove node unset($nodes[$key]); continue 2; } else { throw new InvalidTraverseException('enterNode() returned invalid value of type ' . \gettype($return)); } } } // should traverse node childrens properties? if ($traverseChildren) { $node = $this->traverseNode($node); if ($this->stopTraversal) { break; } } foreach ($this->phpDocNodeVisitors as $visitorIndex => $phpDocNodeVisitor) { $return = $phpDocNodeVisitor->leaveNode($node); if ($return !== null) { if ($return instanceof Node) { $node = $return; } elseif (\is_array($return)) { $doNodes[] = [$key, $return]; break; } elseif ($return === self::NODE_REMOVE) { $doNodes[] = [$key, []]; break; } elseif ($return === self::STOP_TRAVERSAL) { $this->stopTraversal = \true; break 2; } else { throw new InvalidTraverseException('leaveNode() returned invalid value of type ' . \gettype($return)); } } if ($breakVisitorIndex === $visitorIndex) { break; } } } return $nodes; } }